Skip to content

fix(rescan): retain modTimes when purge emit fails#210

Merged
radimsem merged 5 commits into
devfrom
fix/rescan-clears-mod-times-before-emit-succeeds
May 27, 2026
Merged

fix(rescan): retain modTimes when purge emit fails#210
radimsem merged 5 commits into
devfrom
fix/rescan-clears-mod-times-before-emit-succeeds

Conversation

@radimsem

Copy link
Copy Markdown
Owner

Summary

Fixes #207 — a rescan tick that detected a file is gone deleted its entry from r.modTimes before reconcileDeleted actually emitted the purge snapshot. If emitter.Emit then failed (SQLITE_BUSY exhausting Tx retry budget, context cancellation racing the emit, transient I/O), the in-memory tracking was already cleared but the DB rows remained. The deletion was never retried, leaving zombie nodes that surfaced via MemorySearch, snapshot replays, etc. — until a full re-compile.

Fix (issue option a — defer the delete): mirror the discipline already present in the compile path at rescan.go:334 (maps.Copy(r.modTimes, pending) runs after compiler.Compile returns no error). Collect (absPath, relPath) pairs without mutating r.modTimes in the loop; clear r.modTimes only when reconcileDeleted returns ok=true.

  • reconcileDeleted signature: []PurgedFile([]PurgedFile, bool). ok=true for the three no-op success branches (empty deleted, no matching nodes, happy path); ok=false for GetNodesByFiles and Emit errors.
  • notifyChange() gated on ok && len(deleted) > 0 — failed purges don't fire change events.
  • Bool over error per .claude/rules/go-concise.md §5 ("handle or return, never both"): caller doesn't discriminate failure modes, and reconcileDeleted already logs the underlying error internally.

Test plan

  • TestRescanLoop_RetainsModTimesOnPurgeEmitFailure — new; mirrors sibling TestRescanLoop_SkipsPurgeOnWalkError. Injects r.walkFn to wrap the real walk + cancel ctx after the walk returns, so emitter.Emit's BeginTx(ctx, ...) fails. Asserts modTimes preserved + DB nodes intact + no new snapshot row. Then resets walkFn and re-scans to verify the retry path completes the purge.
  • TestRescanLoop_ReconcilesDeletedFiles, TestRescanLoop_PublishesStatus, TestRescanLoop_SkipsPurgeOnWalkError — still green (no regression in happy / walk-error paths).
  • make test, make fmt lint tidy — all green (0 lint issues).

Reviewed by go-style-reviewer (project subagent) + Codex (generic, via /forge ... codex). Loop converged on first pass — zero actionable findings.

🤖 Generated with Claude Code

radimsem and others added 5 commits May 27, 2026 21:43
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@radimsem radimsem self-assigned this May 27, 2026
@radimsem radimsem added the bug Something isn't working label May 27, 2026
@radimsem radimsem merged commit 58fe95b into dev May 27, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug] rescan reconcileDeleted clears modTimes before emit succeeds — failed purge emit leaks DB nodes forever

1 participant